//! Class object impl

use crate::avm2::activation::Activation;
use crate::avm2::class::{AllocatorFn, Class, CustomConstructorFn};
use crate::avm2::error::{
    self, make_error_1070, make_error_1107, make_error_1112, make_error_1127, make_error_1128,
};
use crate::avm2::function::{exec, FunctionArgs};
use crate::avm2::method::{Method, MethodAssociation, NativeMethodImpl};
use crate::avm2::object::function_object::FunctionObject;
use crate::avm2::object::script_object::ScriptObjectData;
use crate::avm2::object::{Object, ScriptObject, TObject};
use crate::avm2::property::Property;
use crate::avm2::scope::{Scope, ScopeChain};
use crate::avm2::value::Value;
use crate::avm2::vtable::VTable;
use crate::avm2::Error;
use crate::avm2::Multiname;
use crate::avm2::QName;
use crate::avm2::TranslationUnit;
use crate::context::UpdateContext;
use crate::string::AvmString;
use fnv::FnvHashMap;
use gc_arena::barrier::unlock;
use gc_arena::{
    lock::{Lock, RefLock},
    Collect, Gc, GcWeak, Mutation,
};
use ruffle_common::utils::HasPrefixField;
use ruffle_macros::istr;
use std::fmt::Debug;
use std::hash::{Hash, Hasher};

/// An Object which can be called to execute its function code.
#[derive(Collect, Clone, Copy)]
#[collect(no_drop)]
pub struct ClassObject<'gc>(pub Gc<'gc, ClassObjectData<'gc>>);

#[derive(Collect, Clone, Copy, Debug)]
#[collect(no_drop)]
pub struct ClassObjectWeak<'gc>(pub GcWeak<'gc, ClassObjectData<'gc>>);

#[derive(Collect, Clone, HasPrefixField)]
#[collect(no_drop)]
#[repr(C, align(8))]
pub struct ClassObjectData<'gc> {
    /// Base script object
    base: ScriptObjectData<'gc>,

    /// The class associated with this class object.
    class: Class<'gc>,

    /// The associated prototype.
    /// Should always be non-None after initialization.
    prototype: Lock<Option<Object<'gc>>>,

    /// The captured scope that all class traits will use.
    class_scope: ScopeChain<'gc>,

    /// The captured scope that all instance traits will use.
    instance_scope: Lock<ScopeChain<'gc>>,

    /// The base class of this one.
    ///
    /// If `None`, this class has no parent. In practice, this is only used for
    /// interfaces (at least by the AS3 compiler in Animate CC 2020.)
    superclass_object: Option<ClassObject<'gc>>,

    /// List of all applications of this class.
    ///
    /// Only applicable if this class is generic.
    ///
    /// It is legal to apply a type with the value `null`, which is represented
    /// as `None` here. AVM2 considers both applications to be separate
    /// classes, though we consider the parameter to be the class `Object` when
    /// we get a param of `null`.
    applications: RefLock<FnvHashMap<Option<Class<'gc>>, ClassObject<'gc>>>,

    /// VTable used for instances of this class.
    ///
    /// Newly-created `ClassObject`s begin with a dummy vtable: the
    /// `init_instance_vtable` method should be called to replace it with the
    /// real vtable.
    instance_vtable: Lock<VTable<'gc>>,
}

impl<'gc> ClassObject<'gc> {
    /// Allocate the prototype for this class.
    ///
    /// This function is not used during the initialization of "early classes",
    /// i.e. `Object`, `Function`, and `Class`. Those classes and their
    /// prototypes are weaved together separately.
    fn allocate_prototype(
        self,
        context: &mut UpdateContext<'gc>,
        superclass_object: Option<ClassObject<'gc>>,
    ) -> Object<'gc> {
        let proto = ScriptObject::new_object(context);

        if let Some(superclass_object) = superclass_object {
            let base_proto = superclass_object.prototype();
            proto.set_proto(context.gc(), base_proto);
        }
        proto
    }

    /// Construct a class.
    ///
    /// This function returns the class constructor object, which should be
    /// used in all cases where the type needs to be referred to. It's class
    /// initializer will be executed during this function call.
    ///
    /// `base_class` is allowed to be `None`, corresponding to a `null` value
    /// in the VM. This corresponds to no base class, and in practice appears
    /// to be limited to interfaces.
    pub fn from_class(
        activation: &mut Activation<'_, 'gc>,
        class: Class<'gc>,
        superclass_object: Option<ClassObject<'gc>>,
    ) -> Result<Self, Error<'gc>> {
        let class_object = Self::from_class_partial(activation, class, superclass_object);

        class_object.bind_methods(activation)?;
        class_object.validate_class(activation)?;
        class_object.run_class_initializer(activation)?;

        Ok(class_object)
    }

    /// Like `from_class`, but skips the method binding, class validation, and
    /// class initializer execution steps. This method is used for Vector.<T>
    /// ClassObject creation, since Vector.<T> classes have their methods
    /// already bound and do not need to be validated or have their initializers
    /// run.
    fn from_class_partial(
        activation: &mut Activation<'_, 'gc>,
        class: Class<'gc>,
        superclass_object: Option<ClassObject<'gc>>,
    ) -> Self {
        let class_object = Self::from_class_minimal(activation, class, superclass_object);

        let class_proto = class_object.allocate_prototype(activation.context, superclass_object);
        class_object.link_prototype(activation.context, class_proto);

        let class_class_proto = activation.avm2().classes().class.prototype();
        class_object.link_type(activation.gc(), class_class_proto);

        class_object.into_finished_class(activation);

        class_object
    }

    /// Allocate a class but do not properly construct it.
    ///
    /// This function does the bare minimum to allocate classes, without taking
    /// any action that would require the existence of any other objects in the
    /// object graph. The resulting class will be a bare object and should not
    /// be used or presented to user code until you finish initializing it. You
    /// do that by calling `link_prototype`, `link_type`, `into_finished_class`,
    /// `bind_methods`, `validate_class`, and `run_class_initializer` in that order.
    ///
    /// This returns the class object directly (*not* an `Object`), to allow
    /// further manipulation of the class once it's dependent types have been
    /// allocated.
    pub fn from_class_minimal(
        activation: &mut Activation<'_, 'gc>,
        class: Class<'gc>,
        superclass_object: Option<ClassObject<'gc>>,
    ) -> Self {
        let c_class = class
            .c_class()
            .expect("Can only call ClassObject::from_class on i_classes");

        let scope = activation.create_scopechain();

        let mc = activation.gc();

        let class_object = ClassObject(Gc::new(
            activation.gc(),
            ClassObjectData {
                // We pass `custom_new` the temporary vtable of the class object
                // because we don't have the full vtable created yet. We'll
                // set it to the true vtable in `into_finished_class`.
                base: ScriptObjectData::custom_new(c_class, None, c_class.vtable()),
                class,
                prototype: Lock::new(None),
                class_scope: scope,
                instance_scope: Lock::new(scope),
                superclass_object,
                applications: RefLock::new(Default::default()),
                instance_vtable: Lock::new(c_class.vtable()),
            },
        ));

        // instance scope = [..., class object]
        let instance_scope = scope.chain(mc, &[Scope::new(class_object.into())]);

        unlock!(
            Gc::write(mc, class_object.0),
            ClassObjectData,
            instance_scope
        )
        .set(instance_scope);
        class_object.init_instance_vtable(activation);

        class.add_class_object(activation.gc(), class_object);

        class_object
    }

    fn init_instance_vtable(self, activation: &mut Activation<'_, 'gc>) {
        let mc = activation.gc();
        let class = self.inner_class_definition();

        let vtable = VTable::new_with_interface_properties(
            class,
            self.superclass_object(),
            Some(self.instance_scope()),
            self.superclass_object().map(|cls| cls.instance_vtable()),
            activation.context,
        );

        unlock!(Gc::write(mc, self.0), ClassObjectData, instance_vtable).set(vtable);
    }

    /// Finish initialization of the class.
    ///
    /// This is intended for classes that were pre-allocated with
    /// `from_class_partial`. It skips two critical initialization steps
    /// that are necessary to obtain a functioning class object:
    ///
    ///  - The `link_type` step, which makes the class an instance of another
    ///    type
    ///  - The `link_prototype` step, which installs a prototype for instances
    ///    of this type to inherit
    ///
    /// Make sure to call them before calling this function, or it may yield an
    /// error.
    ///
    /// This function is also when class trait validation happens. Verify
    /// errors will be raised at this time.
    pub fn into_finished_class(self, activation: &mut Activation<'_, 'gc>) {
        let i_class = self.inner_class_definition();
        let c_class = i_class
            .c_class()
            .expect("ClassObject should have an i_class");

        let class_classobject = activation.avm2().classes().class;

        // class vtable == class traits + Class instance traits
        let class_vtable = VTable::new(
            c_class,
            Some(class_classobject),
            Some(self.class_scope()),
            Some(class_classobject.instance_vtable()),
            activation.gc(),
        );

        self.set_vtable(activation.gc(), class_vtable);
    }

    /// Link this class to a prototype.
    pub fn link_prototype(self, context: &mut UpdateContext<'gc>, class_proto: Object<'gc>) {
        let mc = context.gc();
        let constructor_str = istr!(context, "constructor");

        unlock!(Gc::write(mc, self.0), ClassObjectData, prototype).set(Some(class_proto));
        class_proto.set_dynamic_property(constructor_str, self.into(), mc);
        class_proto.set_local_property_is_enumerable(mc, constructor_str, false);
    }

    /// Manually set the type of this `Class`.
    ///
    /// This is intended to support initialization of early types such as
    /// `Class` and `Object`. All other types should pull `Class`'s prototype
    /// and type object from the `Avm2` instance.
    pub fn link_type(self, gc_context: &Mutation<'gc>, proto: Object<'gc>) {
        self.base().set_proto(gc_context, proto);
    }

    /// Validate signatures of the class.
    pub fn validate_class(self, activation: &mut Activation<'_, 'gc>) -> Result<(), Error<'gc>> {
        let class = self.inner_class_definition();
        let c_class = class.c_class().expect("ClassObject stores an i_class");

        class.validate_signatures(activation)?;
        c_class.validate_signatures(activation)?;

        Ok(())
    }

    /// Bind all the methods declared by the ClassObject to itself.
    pub fn bind_methods(self, activation: &mut Activation<'_, 'gc>) -> Result<(), Error<'gc>> {
        let i_class = self.inner_class_definition();
        let c_class = i_class
            .c_class()
            .expect("ClassObject should have an i_class");

        // Bind all the methods that are declared on the i_class and c_class
        i_class.bind_methods(activation, MethodAssociation::classbound(i_class, false))?;
        c_class.bind_methods(activation, MethodAssociation::classbound(c_class, false))?;

        i_class
            .instance_init()
            .expect("Cannot create ClassObject for Class without init")
            .associate(activation, MethodAssociation::classbound(i_class, false))?;

        // The class initializer (but not the instance initializer) is always
        // in "interpreter mode"
        c_class
            .instance_init()
            .expect("c_class always has initializer")
            .associate(activation, MethodAssociation::classbound(c_class, true))?;

        Ok(())
    }

    /// Run the class's initializer method.
    pub fn run_class_initializer(
        self,
        activation: &mut Activation<'_, 'gc>,
    ) -> Result<(), Error<'gc>> {
        let c_class = self
            .inner_class_definition()
            .c_class()
            .expect("ClassObject stores an i_class");

        let self_value: Value<'gc> = self.into();
        let class_classobject = activation.avm2().classes().class;

        let scope = self.0.class_scope;

        let Some(class_initializer) = c_class.instance_init() else {
            unreachable!("c_classes should always have initializer methods")
        };

        let class_init_fn = FunctionObject::from_method(
            activation.context,
            class_initializer,
            scope,
            Some(self_value),
            Some(class_classobject),
        );

        class_init_fn.call(activation, self_value, FunctionArgs::empty())?;

        Ok(())
    }

    /// Call the instance initializer.
    ///
    /// This method may panic if called with a Null or Undefined receiver.
    pub fn call_init(
        self,
        receiver: Value<'gc>,
        arguments: FunctionArgs<'_, 'gc>,
        activation: &mut Activation<'_, 'gc>,
    ) -> Result<Value<'gc>, Error<'gc>> {
        let scope = self.0.instance_scope.get();
        let method = self.init_method();

        if let Some(method) = method {
            // Provide a callee object if necessary
            let callee = if method.needs_arguments_object() {
                Some(FunctionObject::from_method(
                    activation.context,
                    method,
                    scope,
                    Some(receiver),
                    self.superclass_object(),
                ))
            } else {
                None
            };

            exec(
                method,
                scope,
                receiver,
                self.superclass_object(),
                arguments,
                activation,
                callee,
            )
        } else {
            unreachable!("Cannot instantiate a class without an init method")
        }
    }

    /// Call a method defined in this class.
    ///
    /// This is intended to be called on the class object that is the
    /// superclass of the one that defined the currently called property. If no
    /// such superclass exists, you should use the class object for the
    /// receiver's actual type (i.e. the lowest in the chain). This ensures
    /// that repeated supercalls to the same method will call parent and
    /// grandparent methods, and so on.
    ///
    /// If no method exists with the given name, this falls back to calling a
    /// property of the `receiver`. This fallback only triggers if the property
    /// is associated with a trait. Dynamic properties will still error out.
    ///
    /// This function will search through the class object tree starting from
    /// this class up to `Object` for a method trait with the given name. If it
    /// is found, it will be called with the receiver and arguments you
    /// provided, as if it were defined on the target instance object.
    ///
    /// The class that defined the method being called will also be provided to
    /// the `Activation` that the method runs on so that further supercalls
    /// will work as expected.
    ///
    /// This method corresponds directly to the AVM2 operation `callsuper`,
    /// with the caveat listed above about what object to call it on.
    pub fn call_super(
        self,
        multiname: &Multiname<'gc>,
        receiver: Object<'gc>,
        arguments: FunctionArgs<'_, 'gc>,
        activation: &mut Activation<'_, 'gc>,
    ) -> Result<Value<'gc>, Error<'gc>> {
        let property = self.instance_vtable().get_trait(multiname);
        match property {
            Some(Property::Slot { slot_id }) | Some(Property::ConstSlot { slot_id }) => {
                let func = receiver.get_slot(slot_id);

                func.call(activation, receiver.into(), arguments)
            }
            Some(Property::Method { disp_id }) => {
                self.call_method_super(activation, receiver, disp_id, arguments)
            }
            Some(Property::Virtual { get: Some(get), .. }) => {
                // Call the getter, then `Value::call` the result
                let obj =
                    self.call_method_super(activation, receiver, get, FunctionArgs::empty())?;

                obj.call(activation, receiver.into(), arguments)
            }
            Some(Property::Virtual { get: None, .. }) => Err(error::make_reference_error(
                activation,
                error::ReferenceErrorCode::ReadFromWriteOnly,
                multiname,
                self.inner_class_definition(),
            )),
            None => Err(make_error_1070(
                activation,
                self.inner_class_definition(),
                multiname,
            )),
        }
    }

    /// Call a getter defined in this class.
    ///
    /// This is intended to be called on the class object that is the
    /// superclass of the one that defined the currently called property. If no
    /// such superclass exists, you should use the class object for the
    /// receiver's actual type (i.e. the lowest in the chain). This ensures
    /// that repeated supercalls to the same getter will call parent and
    /// grandparent getters, and so on.
    ///
    /// If no getter exists with the given name, this falls back to getting a
    /// property of the `receiver`. This fallback only triggers if the property
    /// is associated with a trait. Dynamic properties will still error out.
    ///
    /// This function will search through the class object tree starting from
    /// this class up to `Object` for a getter trait with the given name. If it
    /// is found, it will be called with the receiver you provided, as if it
    /// were defined on the target instance object.
    ///
    /// The class that defined the getter being called will also be provided to
    /// the `Activation` that the getter runs on so that further supercalls
    /// will work as expected.
    ///
    /// This method corresponds directly to the AVM2 operation `getsuper`,
    /// with the caveat listed above about what object to call it on.
    pub fn get_super(
        self,
        multiname: &Multiname<'gc>,
        receiver: Object<'gc>,
        activation: &mut Activation<'_, 'gc>,
    ) -> Result<Value<'gc>, Error<'gc>> {
        let property = self.instance_vtable().get_trait(multiname);

        match property {
            Some(Property::Slot { slot_id }) | Some(Property::ConstSlot { slot_id }) => {
                Ok(receiver.get_slot(slot_id))
            }
            Some(Property::Method { disp_id }) => {
                let full_method = self.instance_vtable().get_full_method(disp_id).unwrap();
                let callee = FunctionObject::from_method(
                    activation.context,
                    full_method.method,
                    full_method.scope(),
                    Some(receiver.into()),
                    full_method.super_class_obj,
                );

                Ok(callee.into())
            }
            Some(Property::Virtual {
                get: Some(disp_id), ..
            }) => self.call_method_super(activation, receiver, disp_id, FunctionArgs::empty()),
            Some(Property::Virtual { get: None, .. }) => Err(error::make_reference_error(
                activation,
                error::ReferenceErrorCode::ReadFromWriteOnly,
                multiname,
                self.inner_class_definition(),
            )),
            None => Err(error::make_reference_error(
                activation,
                error::ReferenceErrorCode::InvalidRead,
                multiname,
                self.inner_class_definition(),
            )),
        }
    }

    /// Call a setter defined in this class.
    ///
    /// This is intended to be called on the class object that is the
    /// superclass of the one that defined the currently called property. If no
    /// such superclass exists, you should use the class object for the
    /// receiver's actual type (i.e. the lowest in the chain). This ensures
    /// that repeated supercalls to the same setter will call parent and
    /// grandparent setter, and so on.
    ///
    /// If no setter exists with the given name, this falls back to setting a
    /// property of the `receiver`. This fallback only triggers if the property
    /// is associated with a trait. Dynamic properties will still error out.
    ///
    /// This function will search through the class object tree starting from
    /// this class up to `Object` for a setter trait with the given name. If it
    /// is found, it will be called with the receiver and value you provided,
    /// as if it were defined on the target instance object.
    ///
    /// The class that defined the setter being called will also be provided to
    /// the `Activation` that the setter runs on so that further supercalls
    /// will work as expected.
    ///
    /// This method corresponds directly to the AVM2 operation `setsuper`,
    /// with the caveat listed above about what object to call it on.
    pub fn set_super(
        self,
        multiname: &Multiname<'gc>,
        value: Value<'gc>,
        receiver: Object<'gc>,
        activation: &mut Activation<'_, 'gc>,
    ) -> Result<(), Error<'gc>> {
        let property = self.instance_vtable().get_trait(multiname);
        match property {
            Some(Property::Slot { slot_id }) => {
                // Comment placed for formatting reasons
                receiver.set_slot(slot_id, value, activation)
            }
            Some(Property::Method { .. }) => Err(error::make_reference_error(
                activation,
                error::ReferenceErrorCode::AssignToMethod,
                multiname,
                self.inner_class_definition(),
            )),
            Some(Property::Virtual {
                set: Some(disp_id), ..
            }) => {
                let args = &[value];
                let args = FunctionArgs::from_slice(args);

                self.call_method_super(activation, receiver, disp_id, args)?;

                Ok(())
            }
            Some(Property::ConstSlot { .. }) | Some(Property::Virtual { set: None, .. }) => {
                if activation.is_interpreter() {
                    Err(error::make_reference_error(
                        activation,
                        error::ReferenceErrorCode::WriteToReadOnly,
                        multiname,
                        self.inner_class_definition(),
                    ))
                } else {
                    // In JIT mode in FP, setsuper on const slots and
                    // getter-only accessors is silently ignored
                    Ok(())
                }
            }
            None => Err(error::make_reference_error(
                activation,
                error::ReferenceErrorCode::InvalidWrite,
                multiname,
                self.inner_class_definition(),
            )),
        }
    }

    /// Like `Value::call_method`, but uses the method specifically on this class's
    /// instance vtable, not the instance vtable of the receiver. This is intended
    /// to be used for supercalls.
    fn call_method_super(
        self,
        activation: &mut Activation<'_, 'gc>,
        receiver: Object<'gc>,
        disp_id: u32,
        arguments: FunctionArgs<'_, 'gc>,
    ) -> Result<Value<'gc>, Error<'gc>> {
        let full_method = self.instance_vtable().get_full_method(disp_id).unwrap();

        // Only create callee if the method needs it
        let callee = if full_method.method.needs_arguments_object() {
            Some(FunctionObject::from_method(
                activation.context,
                full_method.method,
                full_method.scope(),
                Some(receiver.into()),
                full_method.super_class_obj,
            ))
        } else {
            None
        };

        exec(
            full_method.method,
            full_method.scope(),
            receiver.into(),
            full_method.super_class_obj,
            arguments,
            activation,
            callee,
        )
    }

    pub fn add_application(
        &self,
        mc: &Mutation<'gc>,
        param: Option<Class<'gc>>,
        cls: ClassObject<'gc>,
    ) {
        unlock!(Gc::write(mc, self.0), ClassObjectData, applications)
            .borrow_mut()
            .insert(param, cls);
    }

    /// Parametrize this class. This does not check to ensure that this class is generic.
    pub fn parametrize(
        &self,
        activation: &mut Activation<'_, 'gc>,
        class_param: Option<Class<'gc>>,
    ) -> ClassObject<'gc> {
        let self_class = self.inner_class_definition();

        if let Some(application) = self.0.applications.borrow().get(&class_param) {
            return *application;
        }

        // if it's not a known application, then it's not int/uint/Number/*,
        // so it must be a simple Vector.<*>-derived class.

        let parameterized_class =
            Class::with_type_param(activation.context, self_class, class_param);

        // NOTE: this isn't fully accurate, but much simpler.
        // FP's Vector is more of special case that literally copies some parent class's properties
        // main example: Vector.<Object>.prototype === Vector.<*>.prototype

        let vector_star_cls = activation.avm2().classes().object_vector;
        let class_object =
            Self::from_class_partial(activation, parameterized_class, Some(vector_star_cls));

        unlock!(
            Gc::write(activation.gc(), self.0),
            ClassObjectData,
            applications
        )
        .borrow_mut()
        .insert(class_param, class_object);

        class_object
    }

    pub fn call(
        self,
        activation: &mut Activation<'_, 'gc>,
        arguments: FunctionArgs<'_, 'gc>,
    ) -> Result<Value<'gc>, Error<'gc>> {
        if let Some(call_handler) = self.call_handler() {
            let arguments = &arguments.to_slice();
            call_handler(activation, self.into(), arguments)
        } else if arguments.len() == 1 {
            arguments
                .get_at(0)
                .coerce_to_type(activation, self.inner_class_definition())
        } else {
            Err(make_error_1112(activation, arguments.len()))
        }
    }

    pub fn construct(
        self,
        activation: &mut Activation<'_, 'gc>,
        arguments: &[Value<'gc>],
    ) -> Result<Value<'gc>, Error<'gc>> {
        self.construct_with_args(activation, FunctionArgs::from_slice(arguments))
    }

    pub fn construct_with_args(
        self,
        activation: &mut Activation<'_, 'gc>,
        arguments: FunctionArgs<'_, 'gc>,
    ) -> Result<Value<'gc>, Error<'gc>> {
        if let Some(custom_constructor) = self.custom_constructor() {
            let arguments = &arguments.to_slice();
            custom_constructor(activation, arguments)
        } else {
            let instance_allocator = self.instance_allocator();

            let instance = instance_allocator(self, activation)?;

            self.call_init(instance.into(), arguments, activation)?;

            Ok(instance.into())
        }
    }

    pub fn translation_unit(self) -> Option<TranslationUnit<'gc>> {
        self.init_method().map(|m| m.translation_unit())
    }

    pub fn init_method(self) -> Option<Method<'gc>> {
        self.inner_class_definition().instance_init()
    }

    pub fn call_handler(self) -> Option<NativeMethodImpl> {
        self.inner_class_definition().call_handler()
    }

    pub fn instance_vtable(self) -> VTable<'gc> {
        self.0.instance_vtable.get()
    }

    pub fn inner_class_definition(self) -> Class<'gc> {
        self.0.class
    }

    pub fn prototype(self) -> Object<'gc> {
        self.0.prototype.get().unwrap()
    }

    pub fn class_scope(self) -> ScopeChain<'gc> {
        self.0.class_scope
    }

    pub fn instance_scope(self) -> ScopeChain<'gc> {
        self.0.instance_scope.get()
    }

    pub fn superclass_object(self) -> Option<ClassObject<'gc>> {
        self.0.superclass_object
    }

    fn instance_allocator(self) -> AllocatorFn {
        self.inner_class_definition().instance_allocator().0
    }

    fn custom_constructor(self) -> Option<CustomConstructorFn> {
        self.inner_class_definition()
            .custom_constructor()
            .map(|c| c.0)
    }

    pub fn name(self) -> QName<'gc> {
        self.inner_class_definition().name()
    }
}

impl<'gc> TObject<'gc> for ClassObject<'gc> {
    fn gc_base(&self) -> Gc<'gc, ScriptObjectData<'gc>> {
        HasPrefixField::as_prefix_gc(self.0)
    }

    fn to_string(&self, mc: &Mutation<'gc>) -> AvmString<'gc> {
        let class_name = self.0.class.name().local_name();

        AvmString::new_utf8(mc, format!("[class {class_name}]"))
    }

    fn apply(
        &self,
        activation: &mut Activation<'_, 'gc>,
        nullable_params: &[Value<'gc>],
    ) -> Result<ClassObject<'gc>, Error<'gc>> {
        let self_class = self.inner_class_definition();

        if !self_class.is_generic() {
            return Err(make_error_1127(activation));
        }

        if nullable_params.len() != 1 {
            return Err(make_error_1128(
                activation,
                self.inner_class_definition(),
                nullable_params.len(),
            ));
        }

        //Because `null` is a valid parameter, we have to accept values as
        //parameters instead of objects. We coerce them to objects now.
        let object_param = match nullable_params[0] {
            Value::Null => None,
            v => Some(v),
        };
        let class_param = match object_param {
            None => None,
            Some(cls) => Some(
                cls.as_object()
                    .and_then(|c| c.as_class_object())
                    .ok_or_else(|| make_error_1107(activation))?
                    .inner_class_definition(),
            ),
        };

        Ok(self.parametrize(activation, class_param))
    }
}

impl PartialEq for ClassObject<'_> {
    fn eq(&self, other: &Self) -> bool {
        Object::ptr_eq(*self, *other)
    }
}

impl Eq for ClassObject<'_> {}

impl Hash for ClassObject<'_> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.as_ptr().hash(state);
    }
}

impl Debug for ClassObject<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        f.debug_struct("ClassObject")
            .field("name", &self.name())
            .field("ptr", &Gc::as_ptr(self.0))
            .finish()
    }
}
